package com.saasovation.common.port.adapter.notification;

import com.saasovation.common.AssertionConcern;
import com.saasovation.common.domain.model.DomainEvent;
import com.saasovation.common.event.EventStore;
import com.saasovation.common.event.StoredEvent;
import com.saasovation.common.notification.Notification;
import com.saasovation.common.notification.NotificationPublisher;
import com.saasovation.common.notification.PublishedNotificationTracker;
import com.saasovation.common.notification.PublishedNotificationTrackerStore;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

import java.util.List;
import java.util.stream.Collectors;

import static com.saasovation.common.port.adapter.notification.SpringMessageUtils.convertToMessage;

public class SpringRabbitMQNotificationPublisher extends AssertionConcern implements NotificationPublisher {

    private RabbitTemplate rabbitTemplate;
    private String exchangeName;
    private EventStore eventStore;
    private PublishedNotificationTrackerStore trackerStore;

    public SpringRabbitMQNotificationPublisher(EventStore anEventStore,
                                               PublishedNotificationTrackerStore trackerStore,
                                               RabbitTemplate rabbitTemplate,
                                               String exchangeName) {
        setEventStore(anEventStore);
        setTrackerStore(trackerStore);
        setRabbitTemplate(rabbitTemplate);
        setExchangeName(exchangeName);
    }

    @Override
    public void publishNotifications() {
        PublishedNotificationTracker tracker = this.trackerStore().publishedNotificationTracker();
        List<Notification> notifications =
                this.listUnpublishedNotifications(tracker.mostRecentPublishedNotificationId());
        for (Notification notification : notifications) {
            sendNotification(notification);
        }
        this.trackerStore().trackMostRecentPublishedNotification(tracker, notifications);
    }

    private void sendNotification(Notification notification) {
        Message message = convertToMessage(notification);
        rabbitTemplate().convertAndSend(exchangeName(), "", message);
    }

    private List<Notification> listUnpublishedNotifications(long aMostRecentPublishedMessageId) {
        List<StoredEvent> storedEvents =
                this.eventStore().allStoredEventsSince(aMostRecentPublishedMessageId);
        return this.notificationsFrom(storedEvents);
    }

    private List<Notification> notificationsFrom(List<StoredEvent> aStoredEvents) {
        return aStoredEvents.stream().map(storedEvent -> {
            DomainEvent domainEvent = storedEvent.toDomainEvent();
            return new Notification(storedEvent.eventId(), domainEvent);
        }).collect(Collectors.toList());
    }

    @Override
    public boolean internalOnlyTestConfirmation() {
        throw new UnsupportedOperationException("Not supported by production implementation.");
    }

    private RabbitTemplate rabbitTemplate() {
        return rabbitTemplate;
    }

    private void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
        assertArgumentNotNull(rabbitTemplate, "rabbitTemplate cannot be null");
        this.rabbitTemplate = rabbitTemplate;
    }

    private String exchangeName() {
        return exchangeName;
    }

    private void setExchangeName(String exchangeName) {
        assertArgumentNotEmpty(exchangeName, "exchangeName cannot be empty");
        this.exchangeName = exchangeName;
    }

    private EventStore eventStore() {
        return eventStore;
    }

    private void setEventStore(EventStore anEventStore) {
        assertArgumentNotNull(anEventStore, "anEventStore cannot be null");
        this.eventStore = anEventStore;
    }

    private PublishedNotificationTrackerStore trackerStore() {
        return trackerStore;
    }

    private void setTrackerStore(PublishedNotificationTrackerStore anTrackerStore) {
        assertArgumentNotNull(anTrackerStore, "anTrackerStore cannot be null");
        this.trackerStore = anTrackerStore;
    }
}
